/**
 * 枚举类型<br/>
 * 注意1：子类声明后务必确保init()被调用一次，方可正常使用
 * 注意2：由于枚举常量作为对象可能被代理，所以枚举常量的比较不要使用===操作符，而应该用equals()方法
 */
const DEFAULT_KEYS = ['ordinal', 'name', 'caption', 'scenes'];

export default class Enum {

    ordinal = 0;
    name = '';
    caption = '';
    scenes = {}; // {sceneName: sceneCaption}

    /**
     * @param caption 显示名称
     * @param {Object|Array=} [scenes] 场景集
     */
    constructor(caption, scenes) {
        this.caption = caption;
        if (scenes) {
            if (Array.isArray(scenes)) {
                for (let scene of scenes) {
                    this.scenes[scene] = this.caption;
                }
            } else if (typeof scenes === 'object') {
                Object.assign(this.scenes, scenes);
            }
        }
    }

    /**
     * @param {?Object} [tnx] tnx
     */
    static init(tnx) {
        let sceneNameSet = new Set();
        this.forEach((constant, ordinal, name) => {
            constant.ordinal = ordinal;
            constant.name = name;
            Object.keys(constant.scenes).forEach(sceneName => sceneNameSet.add(sceneName));
        });
        if (tnx && tnx.app && tnx.app.rpc && typeof tnx.app.rpc.addEnumType === 'function') {
            tnx.app.rpc.addEnumType({
                name: this.name,
                items: this.getItems(),
            });
            // 加入场景子类型
            for (let sceneName of sceneNameSet) {
                tnx.app.rpc.addEnumType({
                    name: this.name,
                    subname: sceneName,
                    items: this.getItems(sceneName),
                });
            }
        }
    }

    static forEach(consumer) {
        let names = Object.keys(this);
        let ordinal = 0;
        for (let name of names) {
            const constant = this[name];
            if (constant.constructor === this) {
                consumer(constant, ordinal++, name);
            }
        }
    }

    /**
     * @param {?String} [sceneName] 场景名称
     */
    static values(sceneName) {
        let values = [];
        this.forEach(constant => {
            if (!sceneName || constant.scenes[sceneName]) {
                values.push(constant);
            }
        });
        return values;
    }

    static valueOf(name) {
        let result = undefined;
        this.forEach(constant => {
            if (constant.name === name) {
                result = constant;
            }
        });
        return result;
    }

    /**
     * 校验指定常量合法性
     * @param {String|Enum} constant 常量或常量名
     * @param {?String} [sceneName] 场景名称
     * @return {Enum} 常量
     */
    static validate(constant, sceneName) {
        let result = undefined;
        if (constant) {
            if (typeof constant === 'string') {
                result = this.valueOf(constant);
            } else if (constant.constructor === this) {
                result = constant;
            } else if (typeof constant.name === 'string') {
                result = this.validate(constant.name);
            }
            if (result && sceneName) {
                if (!result.scenes[sceneName]) {
                    result = undefined;
                }
            }
        }
        if (!result) {
            let message = `无效的 ${this.name} 常量: ${constant}`;
            if (sceneName) {
                message += `，于场景 ${sceneName} 中`;
            }
            throw new Error(message);
        }
        return result;
    }

    static caption(name) {
        let constant = this.values(name);
        return constant ? constant.caption : undefined;
    }

    /**
     * @param {?String} [sceneName] 场景名称
     */
    static getItems(sceneName) {
        let items = [];
        this.forEach(constant => {
            let caption = constant.caption;
            if (sceneName) {
                let sceneCaption = constant.scenes[sceneName];
                if (sceneCaption) {
                    caption = sceneCaption;
                } else {
                    return;
                }
            }
            let item = {
                key: constant.name,
                caption: caption,
            };
            Object.keys(constant).forEach(key => {
                if (!DEFAULT_KEYS.includes(key)) {
                    item[key] = constant[key];
                }
            });
            items.push(item);
        });
        return items;
    }

    toString() {
        return this.name;
    }

    equals(other) {
        return !!(other && other.constructor === this.constructor && other.name === this.name);
    }

    clone() {
        const constant = new Enum(this.caption, this.scenes);
        constant.ordinal = this.ordinal;
        constant.name = this.name;
        return constant;
    }

    supports(sceneName) {
        return !!this.scenes[sceneName];
    }

    localeCompare(other) {
        if (this.ordinal < other.ordinal) {
            return -1;
        } else if (this.ordinal > other.ordinal) {
            return 1;
        } else {
            return 0;
        }
    }

}
